<?php

/***  DOCUMENTATION LAYER

Ceo Captcha Class

Name: CeoCaptcha
Last Update: Sep 2007
Author: Tom at klenwell@gmail.com
License: GNU GPL (http://www.opensource.org/licenses/gpl-license.html)

DESCRIPTION
  A captcha class that hopefully offers incremental improvements on 
  hn_captcha

METHODS
  MAGIC
  CeoCaptcha($debug=0, $oid=null)    *php 4 constructor*
  __construct($debug, $oid)          *php 5 constructor*
  __destruct()  
  
  PUBLIC
  print_d($message, $color='c33')
  print_r()
  dump()
  
  PRIVATE
  _set_session_data()
  _get_session_data()
  _has_session_data()
  _set_filename()
  _set_dirpath()
  
USAGE
  $Captcha = new CeoCaptcha($debug);
  $Captcha->set_img_dir('./img_bin/');
  $Captcha->make('test');
  print $Captcha->display();

NOTES
  A fork of hn_captcha -- for more info on that class, see:
    http://hn273.users.phpclasses.org/browse/package/1569.html
    
  I like the hn_captcha class a lot (assuming it hasn't been cracked yet.)  The
  main reasons for this update are:
  
  (1) Rationalize configuration a bit
  (2) Integrate garbage collection in the main class
  (3) Modularize language customizations  

______________________________________________________________________________*/

// Load File of Base Class
$base_fname = 'parent.class.php';
#$base_dirpath = dirname(__FILE__) . DIRECTORY_SEPARATOR;
#require_once($base_dirpath . $base_fname);


// Class
/*____________________________________________________________________________*/
class CeoCaptcha
{
/* PUBLIC PROPERTIES */
var $debug = 0;
var $class_name = __CLASS__;
var $oid = '';
var $DS = DIRECTORY_SEPARATOR;

// configurable settings
var $id = '';
var $lang = 'en';
var $num_chars = 6;
var $maxtry = 3;
var $session_lockout = 0;   // will lock out for session (rather than one script call)
var $show_refresh_link = 1;
var $TTF_SET = array('BASTARD_.TTF','KOMTXT__.TTF','VeraSeBd.TTF','RAGAB___.TTF','SANFRANL.TTF');
var $minsize = 24;
var $maxsize = 32;
var $maxrotation = 25;
var $noise = 1;
var $websafecolors = 0;
var $badguys_url = "/";
var $secretposition = 21;
var $secretstring = "This is a very secret string. Nobody should know it, =:)";
var $jpg_quality = 80;    // image quality
var $noisefactor = 9;     // this will multiplyed with number of chars 

// garbage collection settings
var $prefix = 'captcha_';
var $collect_garbage_after = 100;
var $maxlifetime = 600;

// paths and dirs
var $data_dir = '';
var $ttf_dir = '';
var $lang_dir = '';
var $img_dir = '';

var $is_submitted = 0;
var $is_solved = 0;
var $is_failed = 0;
var $is_locked_out = 0;

/* PRIVATE PROPERTIES */
var $_trigger_bx = 'bx_submit_captcha';
var $_refresh_bx = 'bx_refresh_captcha';
var $_key = '';
var $_public_key = '';
var $_lx;                    // image width
var $_ly;                    // image height
var $_gd_version = 0;
var $_qstring = '';
var $_public_K = '';
var $_private_K = '';
var $_current_try = 0;
var $_nb_noise = 0;
var $_ttf_file = '';
var $_gc_file = 'gc_counter.txt';
var $_gcc_filepath = '';
var $_r = 255;
var $_g = 255;
var $_b = 255;

var $_filename = '';
var $_dirpath = '';


/* ** MAGIC METHODS ** */
// php4 constructor
function CeoCaptcha($debug=0, $oid=null)
{
  $this->__construct($debug, $oid);
  register_shutdown_function( array($this, '__destruct') );
}
// END constructor

// php5 constructor
function __construct($debug=0, $oid=null)
{
  // default
  $this->debug = $debug;
  $this->oid = ( empty($oid) ) ? $this->class_name : $oid;
  $this->_set_filename();
  $this->_set_dirpath();
  
  // check gd version
  if ( !$this->_gd_enabled() ) return;
  
  // set paths and dir
  $this->data_dir = $this->_dirpath . 'data' . $this->DS;
  $this->ttf_dir = $this->_dirpath . 'ttf' . $this->DS;
  $this->lang_dir = $this->_dirpath . 'lang' . $this->DS;
  
  // debug
  if ( $this->debug ) $this->print_d('debugging is active for oid ' . $this->oid);
  if ( $this->debug ) $this->print_d('constructor complete for class ' . __CLASS__);
}
// END constructor

// destructor
function __destruct()
{
  if ( $this->debug ) $this->print_d("destroying class {$this->class_name} (oid: {$this->oid})");
}
// END destructor



/* ** PUBLIC METHODS ** */
// method: create/process captcha
function make($id='ceo_captcha')
{
  if ( $this->debug ) $this->print_d("building/processing captcha [id: $id]");
  $this->id = $id;

  // look for hack attempts
  if ( $this->_hacker_detected() || ( $this->session_lockout && $this->_is_locked_out() ) )  
  {
    $this->lock_out();
    return;
  }
  
  // initialize object
  $this->_check_ttf();
  $this->_sanity_checks();
  $this->_set_noise_factor();
  $this->_change_typeface();
  $this->_set_img_size();
  $this->_get_request_vals();
  $this->_generate_keys();
  
  // is submitted
  if ( !empty($_POST[$this->_trigger_bx]) ) $this->is_submitted = 1;
  
  // validate
  $this->_check_submission();
  
  // run garbage collection
  $this->_collect_garbage();
  
  return 1;
}
// END method


// method: print
function display()
{
  $html = '';       // return
  if ( $this->debug ) $this->print_d('build captcha form with captcha image');
  
  $this->_set_messages();
  
  if ( $this->is_locked_out ) 
  {
    if ( $this->debug ) $this->print_d('locked out -> return locked out message');
    return $this->MSG['lockout'];
  }  
  
  $try = $this->_get_try(0);
  if ( $this->debug ) $this->print_d("generate a string which contains current try: ($try)");  
  
  // refresh form
  $refresh_form = '';
  if( $this->show_refresh_link )
  {
    $key_tag = $this->_get_public_key_tag();
    $refresh_form = <<<HTML
    
<div class="refresh_block">
<form style="display:inline;" name="captcha2" action="{$_SERVER['PHP_SELF']}{$this->_qstring}" method="post">
<input type="hidden" name="captcharefresh" value="1" />
<input type="hidden" name="hncaptcha" value="{$try}" />
<p class="refresh">
  {$this->MSG['refresh']}{$key_tag}
  <input class="captcha" type="submit" value="{$this->MSG['refresh_bx']}" />
</p>
</form>
</div>
    
HTML;
  }
  
  // set prompt
  $prompt = '';
  if ( $this->is_failed )
  {
    $prompt_msg = '';
    if ( $this->_current_try > 2 && $this->show_refresh_link ) $prompt_msg = $this->MSG['failure'] . $this->MSG['refresh_hint'];
    elseif ( $this->current_try >= 2 ) $prompt_msg = $this->MSG['failure'];
    if ( !empty($prompt_msg) ) $prompt = '<p class="prompt">' . $prompt_msg . '</p>';
  }
  
  // prepare captcha
  $captcha_img_tag = $this->_get_captcha_img_tag();
  
  $html = <<<HTML
  
<div class="ceo_captcha">
<form class="captcha_form" name="captcha1" action="{$_SERVER['PHP_SELF']}{$this->_qstring}" method="post">
<input type="hidden" name="hncaptcha" value="{$try}" />
{$prompt}
<p class="image">{$captcha_img_tag}</p>
<p class="instruct">{$this->MSG['instruct']}</p>
<p class="input">
<input class="captcha" type="text" name="private_key" value="" maxlength="{$this->num_chars}" size="{$this->num_chars}" />
<input class="captcha" type="submit" name="{$this->_trigger_bx}" value="{$this->MSG['submit_bx']}" />
</p>
</form>

{$refresh_form}

</div>
  
HTML;
  
  return $html;
}
// END method


// method: config object
function config($CFG=null, $secure=1)
{
  if ( !is_array($CFG) ) return;
  
  $mode = ( $secure && strcmp('4.2.0', phpversion()) < 0 ) ? 'secure' : 'insecure';
  if ( $this->debug ) $this->print_d("configuring captcha object [mode: $mode]");

  // secure mode
  if ( $mode == 'secure' )
  {
    $_VALID = get_class_vars( get_class($this) );
    foreach( $CFG as $k => $v)
    {
      if ( array_key_exists($k, $_VALID) ) 
      {
        $this->$k = $v;
        if ( $this->debug ) $this->print_d("setting property [$k] to value [$v]");
      }
      else
      {
        if ( $this->debug ) $this->print_d("property [$k] is not valid");
      }
    }
  }
  
  // insecure mode
  else
  {
    foreach( $CFG as $k => $v ) 
    {
      $this->$k = $v;
      if ( $this->debug ) $this->print_d("setting property [$k] to value [$v]");
    }
  }
  
  return;
}
// END method


// method: set image directory (must be accessible to http)
function set_img_dir($path)
{
  if ( $this->debug ) $this->print_d("setting img dir to path [$path]");
  $this->img_dir = $path;
  return;
}
// END method


// method: lock out
function lock_out()
{
  if ( $this->debug ) $this->print_d('locking this user out');
  $this->is_locked_out = 1;
  if ( !session_id() ) session_start(); 
  $_SESSION[$this->oid][$this->id]['is_locked_out'] = 1;
  return;
}
// END method


// method: change typeface
function unlock()
{
  if ( $this->debug ) $this->print_d('unlocking captcha');
  $this->is_locked_out = 0;
  if ( !session_id() ) session_start(); 
  $_SESSION[$this->oid][$this->id]['is_locked_out'] = 0;
  return;
}
// END method


// method: dev
function dev()
{
  $this->print_d(__FUNCTION__ . ' in dev');
  return;
}
// END method


// method: print_d
function print_d($message, $color='#c33')
{
  $_D = debug_backtrace();
  $f = basename($_D[0]['file']);
  $l = $_D[0]['line'];
  $loc = "{$f}:{$l}";
  $out = "<div style='line-height:1.5em; font-family:monospace; color:$color;'>$message <span style='color:#666;'>[$loc]</span></div>";
  $this->_DBG[] = "$loc -> " . strip_tags($message);
  echo $out;
  return;
}
// END method

// method: print_r
function print_r($Mixed)
{
  $return = htmlspecialchars(print_r($Mixed, 1));
  $return = "<pre>$return</pre>";
  return $return;
}
// END method

// method: dump
function dump()
{
  echo $this->print_r($this);
  return;
}
// END method



/* ** PRIVATE METHODS ** */
// method: is this a hack attempt
function _check_submission()
{      
  if ( $this->debug ) $this->print_d('check captcha submission');
  
  if ( !$this->is_submitted )
  {
    if ( $this->debug ) $this->print_d('no submission -> return');
  }
  elseif ( $this->_compare_keys($this->_public_K,$this->_private_K) )
  {
    if ( $this->debug ) $this->print_d('captcha was solved', 'green');
    $this->is_solved = 1;
  }
  elseif ( $this->_current_try > $this->maxtry )
  {
    if ( $this->debug ) $this->print_d("too many tries [{$this->_current_try}] -> locking out", '#660000');
    $this->lock_out();
  }
  else
  {
    if ( $this->debug ) $this->print_d('submission failed', 'red');
    $this->is_failed = 1;
  }
  
  return;
}
// END method


// method: change typeface
function _is_locked_out()
{
  $is = 0;
  if ( !session_id() ) session_start(); 
  if ( isset($_SESSION[$this->oid][$this->id]['is_locked_out']) ) $is = $_SESSION[$this->oid][$this->id]['is_locked_out'];
  $this->is_locked_out = $is;
  if ( $this->debug ) $this->print_d("checking to see if locked out: {$is}");
  return $is;
}
// END method


// method: compare captcha keys
function _compare_keys($public, $private)
{
  $match = 0;
  $captcha_fname = $this->_get_captcha_fname($public);
  
  // when checking, destroy the captcha img file
  if ( file_exists($captcha_fname) )
  {
    $match = strtolower($private) == strtolower($this->_generate_private($public));
    $del = @unlink($captcha_fname) ? 1 : 0;
    if ( $this->debug ) $this->print_d("comparing private/public keys [$private/$public] : $match | del file [$captcha_fname] : $del");
  }
  else
  {
    if ( $this->debug ) $this->print_d('captcha file not found -> failed');
  }

  return $match;
}
// END method


// method: get captcha file name
function _get_captcha_fname($public='')
{
  $fname = '';
  if( $public == '' ) $public = $this->_public_key;
  $fname = $this->img_dir . $this->prefix . $public . '.jpg';
  if ( $this->debug ) $this->print_d("getting captcha filename: $fname");
  return $fname;
}
// END method


// method: get url for filename
function _get_filename_url($public='')
{
  $url = '';
  if( $public == '' ) $public = $this->_public_key;
  $url = str_replace($_SERVER['DOCUMENT_ROOT'],'',$this->img_dir) . $this->prefix . $public . '.jpg';
  if ( $this->debug ) $this->print_d("getting filename url: $url");
  return $url;
}
// END method


// method: get public key tag
function _get_public_key_tag()
{
  return '<input type="hidden" name="public_key" value="'.$this->_public_key.'" />';
}
// END method


// method: get captcha img tag
function _get_captcha_img_tag($img_only=0)
{
  $tag = '';    // return
  if ( $this->debug ) $this->print_d('get captcha img tag');
  $this->_create_captcha_img();
  
  if ( $this->debug ) $this->print_d('title and alt need to be added to lang files', 'blue');
  $title = 'This is a CAPTCHA image. It is used to block robot and hacker exploitation. (See www.captcha.net for more info.)';
  $alt = 'for more info, see www.captcha.ne';
  $url = $this->_get_filename_url();
	$_IS = getimagesize($this->_get_captcha_fname());
  
  // build tag
  $tag = '<img class="captch_img" src="'.$url.'" '.$_IS[3].' alt="'.$alt.'" title="'.$title.'" />';
    
  if ( $img_only ) return $tag;
  
  // add input key
  $tag = $this->_public_key_input() . "\n{$tag}\n";
  return $tag;
}
// END method


// method: create captcha img
function _public_key_input()
{
  return '<input type="hidden" name="public_key" value="'.$this->_public_key.'" />';
}
// END method


// method: create captcha img
function _create_captcha_img()
{
  if ( $this->debug ) $this->print_d('creating captcha image');
  
  // get private key
  $private_key = $this->_generate_private();
  if ( $this->debug ) $this->print_d("fetching private key: $private_key");
  
  // set appropriate functions according to GD-Version & websafecolor setting
  if( $this->_gd_version >= 2 && !$this->websafecolors )
  {
    $fx1 = 'imagecreatetruecolor';
    $fx2 = 'imagecolorallocate';
  }
  else
  {
    $fx1 = 'imageCreate';
    $fx2 = 'imagecolorclosest';
  }
  $image = $fx1($this->_lx,$this->_ly);
  if ( $this->debug ) $this->print_d("Functions image stream / color defs: {$fx1} / {$fx2}");
  
  // set background color
  $this->_set_random_color(224, 255);
	$bg =  @imagecolorallocate($image, $this->_r, $this->_g, $this->_b);
  @ImageFilledRectangle($image, 0, 0, $this->_lx, $this->_ly, $bg);
  if ( $this->debug ) $this->print_d("background color set: {$this->_r}-{$this->_g}-{$this->_b}");

  // allocate 216-color websafe palette to image
  if( $this->_gd_version < 2 || $this->websafecolors ) $this->_make_colors_websafe($image);
  
  // fill background with noise or grid
  if( $this->_nb_noise > 0 )
  {
    if ( $this->debug ) $this->print_d("fill background with noise: {$this->_nb_noise}");
    for($i=0; $i < $this->_nb_noise; $i++)
    {
      srand((double)microtime()*1000000);
      $size	= intval(rand((int)($this->minsize / 2.3), (int)($this->maxsize / 1.7)));
      srand((double)microtime()*1000000);
      $angle	= intval(rand(0, 360));
      srand((double)microtime()*1000000);
      $x		= intval(rand(0, $this->_lx));
      srand((double)microtime()*1000000);
      $y		= intval(rand(0, (int)($this->_ly - ($size / 5))));
      $this->_set_random_color(160, 224);
      $color	= $fx2($image, $this->_r, $this->_g, $this->_b);
      srand((double)microtime()*1000000);
      $text	= chr(intval(rand(45,250)));
      @ImageTTFText($image, $size, $angle, $x, $y, $color, $this->_change_typeface(), $text);
    }
  }
  else
  {
    if ( $this->debug ) $this->print_d('fill background with grid lines');
    
    // horizontal lines
    for( $i=0; $i < $this->_lx; $i += (int)($this->minsize / 1.5) )
    {
      $this->_set_random_color(160, 224);
      $color	= $fx2($image, $this->_r, $this->_g, $this->_b);
      @imageline($image, $i, 0, $i, $this->_ly, $color);
    }
    
    // vertical lines
    for( $i=0 ; $i < $this->_ly; $i += (int)($this->minsize / 1.8) )
    {
      $this->_set_random_color(160, 224);
      $color	= $fx2($image, $this->_r, $this->_g, $this->_b);
      @imageline($image, 0, $i, $this->_lx, $i, $color);
    }
  }
  
  // generate text
  if ( $this->debug ) $this->print_d('fill foreground with chars and shadows');
  for( $i=0, $x = intval(rand($this->minsize,$this->maxsize)); $i < $this->num_chars; $i++ )
  {
    $text	 = strtoupper(substr($private_key, $i, 1));
    srand((double)microtime()*1000000);
    $angle = intval(rand(($this->maxrotation * -1), $this->maxrotation));
    srand((double)microtime()*1000000);
    $size	 = intval(rand($this->minsize, $this->maxsize));
    srand((double)microtime()*1000000);
    $y = intval(rand((int)($size * 1.5), (int)($this->_ly - ($size / 7))));
    $this->_set_random_color(0, 127);
    $color = $fx2($image, $this->_r, $this->_g, $this->_b);
    $this->_set_random_color(0, 127);
    $shadow = $fx2($image, $this->_r + 127, $this->_g + 127, $this->_b + 127);
    @ImageTTFText($image, $size, $angle, $x + (int)($size / 15), $y, $shadow, $this->_change_typeface(), $text);
    @ImageTTFText($image, $size, $angle, $x, $y - (int)($size / 15), $color, $this->_ttf_file, $text);
    $x += (int)($size + ($this->minsize / 5));
  }
  
  // create jpeg image
  $fname = $this->_get_captcha_fname();
  @ImageJPEG($image, $fname, $this->jpg_quality);
	$is_created = file_exists($fname);
  if ( $this->debug ) $this->print_d("Was captcha img [{$fname} / quality: {$this->jpg_quality}] created? $is_created");
	@ImageDestroy($image);
  
  if ( !$is_created ) $this->_fatal('unable to create captcha image');
  return;
}
// END method


// method: generate private key
function _set_random_color($min, $max)
{
  srand((double)microtime() * 1000000);
  $this->_r = intval(rand($min,$max));
  srand((double)microtime() * 1000000);
  $this->_g = intval(rand($min,$max));
  srand((double)microtime() * 1000000);
  $this->_b = intval(rand($min,$max));
  return;
}
// END method


// method: make colors in image websafe
function _make_colors_websafe(&$image)
{
  for($r = 0; $r <= 255; $r += 51)
  {
    for($g = 0; $g <= 255; $g += 51)
    {
      for($b = 0; $b <= 255; $b += 51)
      {
        $color = imagecolorallocate($image, $r, $g, $b);
      }
    }
  }
  
  if ( $this->debug ) $this->print_d('allocating 216 websafe color to image: '. imagecolorstotal($image));
  return;
}
// END method


// method: generate private key
function _generate_private($public='')
{
  $key = '';    // return
  if ( $public == '') $public = $this->_public_key;
  $key = substr(md5($this->key.$public), 16 - $this->num_chars / 2, $this->num_chars);
  return $key;
}
// END method


// method: get request values
function _get_request_vals()
{
  if ( $this->debug ) $this->print_d("collecting request values");
  
  // query string
  $this->_qstring = strlen(trim($_SERVER['QUERY_STRING'])) > 0 ? '?'.strip_tags($_SERVER['QUERY_STRING']) : '';
	#$refresh = $_SERVER['PHP_SELF'].$this->_qstring;
  if ( $this->debug ) $this->print_d("query string: {$this->_qstring}");

  // POST values
  if( isset($_POST['public_key']) )  $this->_public_K = substr(strip_tags($_POST['public_key']),0,$this->num_chars);
	if( isset($_POST['private_key']) ) $this->_private_K = substr(strip_tags($_POST['private_key']),0,$this->num_chars);
  $this->_current_try = isset($_POST['hncaptcha']) ? $this->_get_try() : 0;
	if( !isset($_POST['captcharefresh']) ) $this->_current_try++;
  if ( $this->debug ) $this->print_d("collecting POST vars, current try is : {$this->_current_try}");

  return;
}
// END method


// method: generate keys
function _generate_keys()
{
  $this->_key = md5($this->secretstring);
	$this->_public_key = substr(md5(uniqid(rand(),true)), 0, $this->num_chars);
  if ( $this->debug ) $this->print_d("generating public key: {$this->_public_key}");
  return;
}
// END method


// method: fatal error
function _fatal($message='fatal error')
{
  if ( $this->debug )
  {
    if ( function_exists('debug_backtrace') ) $_TRACE = debug_backtrace();
    $stack = '<pre>' . print_r($_TRACE, 1) . '</pre>'; 
    $this->print_d("fatal error: $message $stack");
  }   
  
  trigger_error($message, E_USER_ERROR);
  return;
}
// END method


// method: is this a hack attempt
function _hacker_detected()
{
  if( $this->debug ) $this->print_d('checking for bot or hacker-like activity');
  
  // trying to manipulate maxtry?
  if ( isset($_GET['maxtry']) || isset($_POST['maxtry']) || isset($_COOKIE['maxtry']) ) return 1;
  
  // trying to debug
  if ( isset($_GET['debug']) || isset($_POST['debug']) || isset($_COOKIE['debug']) ) return 1;
  
  // trying to circumvent security
  if ( isset($_POST['captcharefresh']) && isset($_POST['private_key']) ) return 1;
  
  return 0;
}
// END method


// method: check TTF
function _check_ttf()
{
  if( $this->debug ) $this->print_d('checking for TTF font set');
  $VALID = array();
  
  if ( !is_array($this->TTF_SET) )
  {
    $this->_fatal("font set [TTF_SET] not found");
    return 0;
  }
  
  foreach ( $this->TTF_SET as $_font )
  {
    if ( is_readable($this->ttf_dir . $_font) ) $VALID[] = $_font;
  }
  $this->TTF_SET = $VALID;
  $num_fonts = count($this->TTF_SET);
  
  if ( $num_fonts < 1 )
  {
    $this->_fatal('no valid TrueTypeFont files found');
    return 0;
  }
  
  if( $this->debug ) $this->print_d("{$num_fonts} TTF font files found");
  
  return 1;
}
// END method


// method: change typeface
function _change_typeface()
{
  if( !is_array($this->TTF_SET) )
  {
    $this->_ttf_file = $this->ttf_dir . $this->TTF_SET;
  }
  else
  {
    srand((float)microtime() * 10000000);
    $key = array_rand($this->TTF_SET);
    $this->_ttf_file = $this->ttf_dir . $this->TTF_SET[$key];
  }
  
  #if( $this->debug ) $this->print_d("typeface set to {$this->_ttf_file}");
	return $this->_ttf_file;
}
// END method


// method: sanity checks
function _sanity_checks()
{
  $this->maxtry = ($this->maxtry > 9 || $this->maxtry < 1 ) ? 3 : $this->maxtry;
  $this->secretposition = ($this->secretposition > 32 || $this->secretposition < 1) ? $this->maxtry : $this->secretposition;
  
  if( $this->minsize > $this->maxsize )
  {
    $temp = $this->minsize;
    $this->minsize = $this->maxsize;
    $this->maxsize = $temp;
  }
  
  return;
}
// END method


// method: set image size
function _set_img_size()
{
  $this->_lx = ($this->num_chars + 1) * (int)(($this->maxsize + $this->minsize) / 1.5);
  $this->_ly = (int)(2.4 * $this->maxsize);
  if( $this->debug ) $this->print_d("Set image dimensions to {$this->_lx}x{$this->_ly}");
  return;
}
// END method


// method: is this a hack attempt
function _set_noise_factor()
{
  $this->_nb_noise = $this->noise ? ($this->num_chars * $this->noisefactor) : 0;
  if( $this->debug ) $this->print_d("Set number of noise characters to: {$this->_nb_noise}");
  return;
}
// END method


// method: parse attempt number from obfuscating string
function _get_try($in=1)
{
  if ( $this->debug ) $this->print_d('getting attempt number');
  
  $S = array();
  $a = $b = '';
  
  for($i = 1; $i <= $this->maxtry; $i++) $S[$i] = $i;

  if ( $in )
  {
    return (int)substr(strip_tags($_POST['hncaptcha']),($this->secretposition -1),1);
  }
  
  for( $i = 1; $i < $this->secretposition; $i++ )
  {
    srand((double)microtime()*1000000);
   	$a .= $S[intval(rand(1,$this->maxtry))];
  }
  
  for($i = 0; $i < (32 - $this->secretposition); $i++)
  {
    srand((double)microtime()*1000000);
    $b .= $S[intval(rand(1,$this->maxtry))];
  }

  return $a.$this->_current_try.$b;
}
// END method


// method: dev
function _set_messages()
{
  if ( $this->debug ) $this->print_d("setting messages to language: {$this->lang}");
  
  // defaults
  $this->MSG['submit_bx'] = 'submit';
  $this->MSG['refresh_bx'] = 'get new image';
  $this->MSG['refresh'] = 'Click here if you cannot read this:';
  $this->MSG['instruct'] = 'Please type the characters above and submit (you can use lowercase letters).';
  $this->MSG['failure'] = 'Your entry did not match the image.  Please try again.';
  $this->MSG['refresh_hint'] = '<br /><small>Note: If you cannot read the letters, you can request a new image.</small>';
  $this->MSG['lockout'] = '<div class="captcha_locked">this captcha has been locked</div>';
  
  // set filepath for lang file
  $path = $this->lang_dir . $this->lang . '.inc.php';
  
  if ( !is_readable($path) )
  {
    trigger_error("lang file [$path] not found, language set to 'en'", E_USER_WARNING);
    $path = $this->lang_dir . 'en.inc.php';
  }
  
  include_once($path);
  return;
}
// END method


// method: make sure gd library is available
function _gd_enabled()
{
  $this->_gd_version = $this->_get_gd_version();
  if ( $this->debug ) $this->print_d("testing for gd library version [major version: {$this->_gd_version}]");
  
  if ( !$this->_gd_version )
  { 
    $this->_fatal('GD Library not found -- required for this class');
    return 0;
  }

  return 1;
}
// END method


// method: get gd version number
function _get_gd_version()
{
  static $gd_version_number = null;
  
  // return previous values
  if ( !is_null($gd_version_number) ) return $gd_version_number;

  // capture php info
  ob_start();
  phpinfo(8);
  $module_info = ob_get_contents();
  ob_end_clean();
  
  // parse version
  $regex = "/\bgd\s+version\b[^\d\n\r]+?([\d\.]+)/i";
  if ( preg_match($regex, $module_info, $matches) ) $gd_version_number = $matches[1];
  else $gd_version_number = 0;

  return $gd_version_number;
}
// END method


// method: delete old files
function _collect_garbage()
{
   if ( $this->debug ) $this->print_d('running garbage collection check');
  
  // set counter file path
  $this->_gcc_filepath = $this->data_dir . $this->_gc_file;
  
  // get counter value
  $counter = $this->_get_gc_counter();
  if ( $counter_val === FALSE )
  {
    trigger_error('unable to retrieve gc counter -- garbage collect aborted', E_USER_WARNING);
    return;
  }
  
  // update counter value
  if ( $this->_update_gc_counter($counter+1) == FALSE )
  {
    trigger_error('unable to update gc counter -- garbage collect aborted', E_USER_WARNING);
    return;
  }
  
  // if counter exceeds limit, delete files and reset counter
  if( $counter >= $this->collect_garbage_after )
  {
    if ( $this->debug ) $this->print_d('time to collect the garbage', 'black');
    if ( $this->_delete_old_img_files() )
    {
      if ( $this->debug ) $this->print_d('the garbage has been taken out! (old image files removed)');
      $this->_update_gc_counter(0);
    }
  }
  
  return;
}
// END method


// method: delete old image files
function _delete_old_img_files()
{
  $is_deleted = false;
  $captchas = 0;
  $trashed = 0;
  
  if ( !$Handle = @opendir($this->img_dir) )
  {
    trigger_error('unable to open img directory to delete files', E_USER_WARNING);
    return $is_deleted;
  }
  
  $is_deleted = true;
  
  while ( false !== ( $file = readdir($Handle) ) )
  {
    if( !is_file($this->img_dir . $file ) ) continue;
    
    if( substr($file,0,strlen($this->prefix)) != $this->prefix ) continue;
    
    if ( strrchr($file, ".") == ".jpg" )
    {
      $captchas++;
      if ( (time() - filemtime($this->img_dir.$file)) >= $this->maxlifetime )
      {
        if ( $is_deleted = @unlink($this->img_dir.$file) ) $trashed++; 
      }
    }
  }
  
  closedir($Handle);
  if ( $this->debug ) $this->print_d("{$captchas} img files in img dir [{$this->img_dir}] -- {$trashed} deleted", 'red');
  
  return $is_deleted;
}
// END method


// method: get gc counter
function _get_gc_counter()
{
  $counter = 0;   // return

  // does file exist?
  if ( !is_file($this->_gcc_filepath) ) touch($this->_gcc_filepath);
  if ( !is_file($this->_gcc_filepath) )
  {
    trigger_error("file [{$this->_gcc_filepath}] is untouchable", E_USER_WARNING);
    return false;
  }
  
  // is readable
  if ( !is_readable($this->_gcc_filepath) )
  {
    trigger_error("file [{$this->_gcc_filepath}] is not read/write", E_USER_WARNING);
    return false;
  }
  
  // open file
  if ( !$FileHandle = @fopen($this->_gcc_filepath, "r") )
  {
    trigger_error("unable to open file [{$this->_gcc_filepath}]", E_USER_WARNING);
    return false;
  }
 
  // get counter value
  $counter = (int)trim(fgets($FileHandle));
  fclose($FileHandle);
  
  return $counter;
}
// END method


// method: update gc counter
function _update_gc_counter($counter)
{
  if ( $this->debug ) $this->print_d("updating gc counter: $counter");

  // does file exist?
  if ( !is_file($this->_gcc_filepath) ) touch($this->_gcc_filepath);
  if ( !is_file($this->_gcc_filepath) )
  {
    trigger_error("file [{$this->_gcc_filepath}] is untouchable", E_USER_WARNING);
    return false;
  }
  
  // is readable
  if ( !is_writable($this->_gcc_filepath) )
  {
    trigger_error("file [{$this->_gcc_filepath}] is not read/write", E_USER_WARNING);
    return false;
  }
  
  // rewrite
  if ( !$Handler = @fopen($this->_gcc_filepath, 'w') )
  {
    trigger_error("unable to write file [{$this->_gcc_filepath}]", E_USER_WARNING);
    return false;
  }
  
  if ( !fputs($Handler,$counter) ) return false;
  fclose($Handler);

  return true;
}
// END method


// method: _set_session_data
function _set_session_value($name, $var)
{
  // initialize session
  if ( !session_id() ) session_start(); 
  $_SESSION[$this->oid][$name] = $var;
  
  return;
}
// END method

// method: get session data
function _get_session_data()
{
  // initialize session
  if ( !$this->_has_session_data() ) return; 
    
  // retrieve session variables
  // $this->var = $_SESSION[$this->oid]['var'];
  
  return;
}
// END method

// method: has session data
function _has_session_data()
{
  // initialize session
  if ( !session_id() ) session_start(); 
    
  // retrieve session variables
  if ( empty($_SESSION[$this->oid]) ) return 0;
  else return 1;
}
// END method

function _set_filename() { $this->_filename = basename(__FILE__); }
function _set_dirpath() { $this->_dirpath = dirname(__FILE__) . $this->DS; }

} // end class
/*____________________________________________________________________________*/

?>
